Akka Remoteを試す


概要

akkaのサンプルを落としてきて、remoteActorについて学ぶ。


解説用サンプル

https://github.com/sassembla/ScalaAkkaRemoteActorWithGradle



準備

サンプルならそのまま動く。


以下は、自分で環境から用意しようぜ、的な話。



まずはakkaをゲットしよう。


おぞましい事になる。

git clone git://github.com/akka/akka.git

で、手元にakkaが落ちてくる

重い

とっても重い


しかも70%超えたあたりから胸をしめつけるような重さ

重い



sbtでプロジェクトを走らせる

都合、2以上のプロジェクトを走らせる必要がある。

ひとつひとつをターミナルで実行する。



まずは一つ目、CalcApp

akkaのフォルダで、

env JAVA_OPTS="-Xmx2048m" sbt

予めPermGen space 1Gで確保しとく。

これがまたえらい時間がかかる。


なんか一段落着いたら、

akka >

が表示されて止まると思う。


試したい該当のプロジェクトは、


akka/akka-samples/akka-sample-remote


なので、

project akka-sample-remote


で該当プロジェクトを定めて、

run



落としたそのまま実行しようとすると、

Multiple main classes detected, select one to run:


 [1] sample.remote.calculator.CalcApp
 [2] sample.remote.calculator.CreationApp
 [3] sample.remote.calculator.LookupApp


とか聞いてくる。

README読むと解るけど、3つアプリが入ってる。

最初は1 CalcApp を選択。

Enter number: 1


Started Calculator Application - waiting for messages

とか帰ってくるので、この状態で放置。



次に、LookupApp

別のターミナルを

akkaのフォルダで立ち上げ、

同じ手順で、3を選ぶ。

Enter number: 3


同じ手順で、3を選ぶ。


するとこうなるterminals.png

左が1 CalcApp 

右が3 LookupApp

2つのTerminalが連動しているのが解ると思う。


(右 LookupAppが計算問題を出し、)

左 CalcAppが計算問題に答え、

右 LookupAppが受け取った結果を表示

という事をしている。

通信はすべてActorを通して、サーバはNettyが使用されている。



内容解説

ここまでのセットアップ後、最小範囲での依存解決とかをして、

あとで自分が忘れても解りやすいようにしたものを、Gradleでビルドしておいた。


https://github.com/sassembla/ScalaAkkaRemoteActorWithGradle

(この記事トップのものと同じ)


①CalcApp側.1

Actorを起動して、放置。

RemoteActorの通信が来るのを待ち受ける。


class CalculatorApplication extends Bootable {
	//#setup
	val system = ActorSystem("CalculatorApplication", ConfigFactory.load.getConfig("calculator"))


このコンフィグがキモで、

resources/ 下の conf ファイルがそれ。

application.conf から一部抜粋すると、


//#calculator
calculator {
  include "common"
	  include "reference"
	
  akka {
    remote.netty.max-total-memory-size = 0b
    remote.netty.max-channel-memory-size = 0b
    remote.netty.execution-pool-size = 4
    remote.netty.execution-pool-keepalive = 60s
    remote.netty.backlog = 4096
    remote.netty.connection-timeout = 120s
    remote.netty.outbound-local-address = "auto"
    remote.netty.message-frame-size = 1 MiB
    remote.netty.reconnect-delay = 5s
    remote.netty.all-timeout = 0s
    remote.netty.write-timeout = 0s
    remote.netty.read-timeout = 0s
    remote.netty.reconnection-time-window = 600s
    remote.netty.backoff-timeout = 0ms
    remote.netty.secure-cookie = ""
    remote.netty.require-cookie = off
    remote.netty.use-passive-connections = on
    remote.backoff-timeout = 0ms
    remote.untrusted-mode = off
    remote.remote-daemon-ack-timeout = 30s
    remote.transport = "akka.remote.netty.NettyRemoteTransport"
    remote.log-received-messages = on
    remote.log-sent-messages = on
    remote.netty.port = 2552
  }
}
//#calculator


と、remoteCreationに関する設定


//#remotecreation
remotecreation {
  include "common"

  akka {
    actor {
      deployment {
        /advancedCalculator {
          remote = "akka://CalculatorApplication@127.0.0.1:2552"
        }
      }
    }
    remote.netty.max-total-memory-size = 0b
    remote.netty.max-channel-memory-size = 0b
    remote.netty.execution-pool-size = 4
    remote.netty.execution-pool-keepalive = 60s
    remote.netty.backlog = 4096
    remote.netty.connection-timeout = 120s
    remote.netty.outbound-local-address = "auto"
    remote.netty.message-frame-size = 1 MiB
    remote.netty.reconnect-delay = 5s
    remote.netty.all-timeout = 0s
    remote.netty.write-timeout = 0s
    remote.netty.read-timeout = 0s
    remote.netty.reconnection-time-window = 600s
    remote.netty.backoff-timeout = 0ms
    remote.netty.secure-cookie = ""
    remote.netty.require-cookie = off
    remote.netty.use-passive-connections = on
    remote.untrusted-mode = off
    remote.remote-daemon-ack-timeout = 30s
    remote.log-received-messages = on
    remote.log-sent-messages = on
    remote.transport = "akka.remote.netty.NettyRemoteTransport"
    remote.netty.port = 2554
  }
}
//#remotecreation


で、hostNameの設定だけは common.conf に出してある。


ひとまず起動すると、このサーバは単純に「待つ」。



②LookupApp側.1

Actor立ち上げ後、下記コード部分でActorSystemへの参加と、すでに起動しているCalcAppのActorの呼び出しをしている。


class LookupApplication extends Bootable {
	//#setup
	val system = ActorSystem("LookupApplication", ConfigFactory.load.getConfig("remotelookup"))
	val actor = system.actorOf(Props[LookupActor], "lookupActor")
	val remoteActor = system.actorFor("akka://CalculatorApplication@127.0.0.1:2552/user/simpleCalculator")


ローカルの2552ポートへとアクセス、名前(CalculatorApplication)とクラス情報(/user/simpleCalculato)でもって、CalcAppの呼び出しを発生させる。

ここで、2554ポートを使うと、remoteCreationが発生すると思う。今回のCalcAppははじめからActor込みで待ってるけど。


で、LookupApp は起動後、下記コードで

while (true) {
	if (Random.nextInt(100) % 2 == 0) app.doSomething(Add(Random.nextInt(100), Random.nextInt(100)))
	else app.doSomething(Subtract(Random.nextInt(100), Random.nextInt(100)))


	Thread.sleep(200)
}


定期的にappのdoSomethingメソッドを、ランダムな数値を2つ入れて実行。

doSomethingメソッドは、


def doSomething(op: MathOp) = {
	actor ! (remoteActor, op)
 }


な感じで、remoteActorに対して、actor経由でop(足したり引いたりとかのオペランドが入ってる型のオブジェクト)を

送りつける。

ここからはCalcApp側が受けるはなし



③CalcApp側.2

receive周りはこんな感じ


class SimpleCalculatorActor extends Actor {
	def receive = {
		case Add(n1, n2) 
			println("Calculating %d + %d".format(n1, n2))
			sender ! AddResult(n1, n2, n1 + n2)
		case Subtract(n1, n2) 
			println("Calculating %d - %d".format(n1, n2))
			sender ! SubtractResult(n1, n2, n1 - n2)
	}
}


Addがきたら、足してsenderに返す。とかしてる。

sender = LookupAppなので、返ると、、、



④LookupApp側.2

こちらのreceiverは


class LookupActor extends Actor {
	def receive = {
		case (actor: ActorRef, op: MathOp)  actor ! op
		case result: MathResult  result match {
			case AddResult(n1, n2, r)       println("Add result: %d + %d = %d".format(n1, n2, r))
			case SubtractResult(n1, n2, r)  println("Sub result: %d - %d = %d".format(n1, n2, r))
		}
	}
}


で、数式の結果(AddResult or SubtractResult)がきたら、プリントしてる。


という感じ。